/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.mybatis.plugins.sqlparser;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.springframework.util.CollectionUtils;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * SqlParser
 *
 * @auther ebert_chan
 */
public abstract class SqlParser {

    // 偏移量
    protected static final String PARAMETER_OFFSET = "PAGE_OFFSET";

    // PARAMETER_END = 偏移量+每页显示数量
    protected static final String PARAMETER_END = "PAGE_END";

    // 每页显示数量
    protected static final String PARAMETER_LIMIT = "PAGE_LIMIT";

    // private static final Pattern SELECT_SQL_PATTERN = Pattern.compile("(?s)(?i)^\\s*SELECT\\s+.*");

    private static final Pattern GROUP_BY_SQL_PATTERN = Pattern.compile("(?s)(?i)\\s+GROUP\\s+BY\\s+");

    private static final Pattern QUERY_SQL_PATTERN = Pattern.compile(
            "(?s)(?i)\\s*(SELECT\\s+)(.*?)(\\s+FROM\\s+)(.*?)(\\s+ORDER\\s+BY\\s+[^\\s,]+(\\s*|\\s+ASC\\s*|\\s+DESC\\s*)?(,{1}\\s*[^\\s,]+(\\s*|\\s+ASC\\s*|\\s+DESC\\s*))*)?$");

    /**
     * 转化为select count语句
     *
     * @param selectSql
     * @return
     * @throws SQLException
     * @auther ebert_chan
     */
    public String selectCountSql(String selectSql) throws SQLException {

        Matcher matcher = GROUP_BY_SQL_PATTERN.matcher(selectSql);
        if (matcher.find()) {
            return this.wrapSelectCountSql(selectSql);
        }

        matcher = QUERY_SQL_PATTERN.matcher(selectSql);
        if (matcher.find()) {
            return matcher.replaceAll("$1COUNT(0)$3$4");
        }

        return this.wrapSelectCountSql(selectSql);
    }

    /**
     * select包装查询语句
     *
     * @param selectSql
     * @return
     * @auther ebert_chan
     */
    private String wrapSelectCountSql(String selectSql) {
        StringBuilder sb = new StringBuilder();
        sb.append("SELECT COUNT(0) FROM ( ").append(selectSql).append(" ) AS TEMP_PAGE_PLUGIN");
        return sb.toString();
    }

    // private void checkDMLSelectSql(String selectSql) throws SQLException {
    // Matcher matcher = SELECT_SQL_PATTERN.matcher(selectSql);
    // if (!matcher.find()) {
    // throw new SQLException("不是有效的SELECT查询语句，请在调用PageHelper.startPage(...)方法之后，紧接着执行SELECT语句。");
    // }
    // }

    /**
     * 转化为select分页语句
     *
     * @param mappedStatement
     * @param parameterObject
     * @param pageIndex
     * @param pageSize
     * @return
     * @throws SQLException
     * @auther ebert_chan
     */
    public BoundSql selectPageBoundSql(MappedStatement mappedStatement, Object parameterObject, int pageIndex, int pageSize) throws SQLException {
        Configuration configuration = mappedStatement.getConfiguration();
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String originalSql = boundSql.getSql();

        List<ParameterMapping> copyParameterMappings = null;
        if (!CollectionUtils.isEmpty(parameterMappings)) {
            copyParameterMappings = new ArrayList<>(parameterMappings.size() + 2);
            copyParameterMappings.addAll(parameterMappings);
        } else {
            copyParameterMappings = new ArrayList<>(2);
        }

        String sql = this.toDialectPageSql(originalSql);

        BoundSql newBoundSql = new BoundSql(configuration, sql, copyParameterMappings, parameterObject);
        this.setParameters(configuration, newBoundSql, copyParameterMappings, pageIndex, pageSize);

        return newBoundSql;
    }

    /**
     * 设置动态sql查询参数值
     *
     * @param configuration
     * @param boundSql
     * @param parameterMappings
     * @param pageIndex
     * @param pageSize
     * @auther ebert_chan
     */
    public abstract void setParameters(Configuration configuration, BoundSql boundSql, List<ParameterMapping> parameterMappings, int pageIndex, int pageSize);

    /**
     * 转化为对应数据库分页语句
     *
     * @param sql
     * @return
     * @auther ebert_chan
     */
    public abstract String toDialectPageSql(String sql);

}
